/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.fresh.bus.canoe.server.support;

import com.fresh.bus.canoe.command.CallbackEntry;
import com.fresh.bus.canoe.command.CallbackEvent;
import com.fresh.bus.canoe.command.CommandEntry;
import com.fresh.bus.canoe.command.CommandMapping;
import com.fresh.bus.canoe.exception.ServerException;
import com.fresh.bus.canoe.message.Request;
import com.fresh.bus.canoe.message.Response;
import com.fresh.bus.canoe.message.ResponseList;
import com.fresh.bus.canoe.server.LifecycleHandler;
import com.fresh.bus.canoe.session.Role;
import com.fresh.bus.canoe.session.Session;
import com.fresh.bus.canoe.session.SessionRoleManager;
import com.fresh.bus.chat.util.MessageUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author panzd
 */
public class DefaultLifecycleHandler implements LifecycleHandler {

    private static final Logger log = LoggerFactory.getLogger(DefaultLifecycleHandler.class);
    private SessionRoleManager sessionRoleManager;
    private CommandMapping commandMapping;

    /**
     * @param commandMapping the commandMapping to set
     */
    public void setCommandMapping(CommandMapping commandMapping) {
        this.commandMapping = commandMapping;
    }

    /**
     * @param sessionRoleManager the sessionRoleManager to set
     */
    public void setSessionRoleManager(SessionRoleManager sessionRoleManager) {
        this.sessionRoleManager = sessionRoleManager;
    }

    @Override
    public void onConnected(Session session) {
        invokeCallbacks(commandMapping.getCallbackEntries(CallbackEvent.ONCNNECTED), session);
    }

    @Override
    public void onDisconnected(Session session) {
        exit(session.getRole(), session);

        invokeCallbacks(commandMapping.getCallbackEntries(CallbackEvent.ONDISCONNECTED), session);
    }

    @Override
    public void onRequest(Session session, Request request) {
        short opCode = request.getOpcode();
        CommandEntry commandEntry = commandMapping.getCommandEntry(opCode);

        Response errorResponse = null;

        if (commandEntry == null) {
            errorResponse = MessageUtils.createErrorResponse(ServerException.UNIMPLEMENTED_OPRATION, opCode);
        } else if (!commandEntry.isRoleRequired() || session.getRole() != null) {
            try {
                invokeCommand(commandEntry, session, request);
            } catch (ServerException e) {
                errorResponse = MessageUtils.createErrorResponse(e.getErrorCode(), opCode);
            }
        } else {
            errorResponse = MessageUtils.createErrorResponse(ServerException.ROLE_REQUIRED, opCode);
        }

        if (errorResponse != null) {
            sendResponse(errorResponse, session);
        }
    }

    private void invokeCommand(CommandEntry commandEntry, Session session, Request request) {
        long start = System.currentTimeMillis();

        Role prevRole = session.getRole();
        Object result = commandEntry.getCommand().invoke(session, request);
        Role currRole = session.getRole();

        if (prevRole != null && !prevRole.equals(currRole)) {
            exit(prevRole, session);
        }

        if (currRole != null && !currRole.equals(prevRole)) {
            sessionRoleManager.join(currRole, session);
            invokeCallbacks(commandMapping.getCallbackEntries(CallbackEvent.ONJOINED), session);
        }

        processCommandResult(commandEntry, result, session);

        log.info("invoke "+ commandEntry + " cost " + (System.currentTimeMillis() - start) + "ms");
    }

    private void processCommandResult(CommandEntry commandEntry, Object result, Session session) {
        if (result == null) {
            return;
        } else if (result instanceof Response) {
            sendResponse((Response) result, session);
        } else if (result instanceof Response[]) {
            sendResponses((Response[]) result, session);
        } else if (result instanceof ResponseList) {
            sendResponses((ResponseList) result, session);
        } else {
            throw new ServerException(ServerException.INVALID_COMMAND, "invalid command " + commandEntry);
        }
    }

    private void sendResponses(Response[] responses, Session session) {
        if (responses == null) {
            return;
        }

        for (Response response : responses) {
            sendResponse(response, session);
        }
    }

    private void sendResponses(ResponseList responses, Session session) {
        if (responses == null) {
            return;
        }

        for (Response response : responses) {
            sendResponse(response, session);
        }
    }

    public void sendResponse(Response response, Session session) {
        if (response == null || session == null) {
            return;
        }

        Iterable<Role> recievers = response.getRecievers();

        if (recievers == null) {
            session.send(response);
        } else {
            for (Role reciever : recievers) {
                Session recSession = sessionRoleManager.getSession(reciever);
                if (recSession != null) {
                    recSession.send(response);
                }
            }
        }
    }

    private void invokeCallbacks(Iterable<CallbackEntry> callbackEntries, Session session) {
        if (callbackEntries == null) {
            return;
        }

        for (CallbackEntry callbackEntry : callbackEntries) {
            long startTime = System.currentTimeMillis();
            Response response = callbackEntry.getCallback().invoke(session);
            sendResponse(response, session);

            log.info("invoke " + callbackEntry + " cost " + (System.currentTimeMillis() - startTime) + " ms");
        }
    }

    private void exit(Role role, Session session) {
        if (role != null) {
            invokeCallbacks(commandMapping.getCallbackEntries(CallbackEvent.ONEXITED), session);
            sessionRoleManager.exit(role);
        }
    }
}
